﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using OpenLS.Spreadsheet.Formats.Biff.Records;

namespace OpenLS.Spreadsheet.Formats.Biff.OfficeArt
{
#if DEBUG
    [ContractClass(typeof (OfficeArtRecordContract))]
#endif
    internal abstract class OfficeArtRecord
    {
        public override string ToString()
        {
            return base.ToString();
        }

        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        protected ushort rhrecInstance
        {
            get { return rh.recInstance; }
        }

        protected byte rhrecVer
        {
            get { return rh.recVer; }
        }

        protected RecType rhrecType
        {
            get { return rh.recType; }
        }

        protected uint rhrecLen
        {
            get { return rh.recLen; }
        }

#if DEBUG
        internal OfficeArtRecordHeader rh;
#else
        private OfficeArtRecordHeader rh;
#endif
        public abstract RecType RecordType { get; }
#if DEBUG
        [ContractClassFor(typeof (OfficeArtRecord))]
        public abstract class OfficeArtRecordContract : OfficeArtRecord
        {
            public override RecType RecordType
            {
                get
                {
                   // Contract.Ensures(ValidTypes());
                    Contract.Ensures(GetType().Name == Contract.Result<RecType>().ToString());
                    Contract.Ensures(GetType().Name == Contract.Result<RecType>().ToString());
                    Contract.Ensures((int) Contract.Result<RecType>() >= 0xF000);

                    throw new NotSupportedException();
                }
            }

            protected override void ReadNoHeader(OfficeArtReadContext context)
            {
                Contract.Requires(rhrecType == RecordType);
                Contract.Requires(rhrecVer == Version);
                Contract.Requires(rhrecLen + context.BaseStream.Position <= context.BaseStream.Length);
                Contract.Ensures(rhrecLen ==
                                 context.BaseStream.Position - Contract.OldValue(context.BaseStream.Position));
                Contract.Ensures(rhrecLen ==
                                 context.BaseStream.Position - Contract.OldValue(context.BaseStream.Position));
                //\\Contract.Ensures(rhrecLen == ByteLength);
                
                throw new NotSupportedException();
            }

#if DEBUG
            private static int tabCount;

            public static bool ChangeTagCount(int diff)
            {
                tabCount += diff;
                return true;
            }

            public static bool DebugWrite(string s)
            {
                for (int i = 0; i < tabCount; i++)
                    Debug.Write("\t");
                Debug.WriteLine(s);
                return true;
            }
#endif

            protected override void WriteNoHeader(OfficeArtWriteContext context)
            {
                Contract.Ensures(ByteLength ==
                                 context.BaseStream.Position - Contract.OldValue(context.BaseStream.Position));
                throw new NotSupportedException();
            }

            public static bool ValidTypes()
            {
                IEnumerable<RecType> values = Enumerable.OfType<RecType>(Enum.GetValues(typeof (RecType)));
                values = Enumerable.Where(values, v => v != 0);
                Contract.Assert(Enumerable.Count(values) == Enumerable.Count(Enumerable.Distinct(values)));
                return true;
            }
        }
#endif

        public void Read(OfficeArtReadContext context)
        {
#if DEBUG
            ReadWriteChecker.CheckOfficeArtClass((t, r) => t.Read(r),
                                                 (t, w) => t.Write(w), context, this);
#endif
            rh = new OfficeArtRecordHeader();
            rh.Read(context.Reader);
#if DEBUG
            long position = context.BaseStream.Position;
#endif
            Contract.Assert(rh.recType == RecordType);
            ReadNoHeaderW(context);
#if DEBUG
          //\\  Contract.Assert(context.BaseStream.Position - position == rh.recLen);
#endif
        }

        private void ReadNoHeaderW(OfficeArtReadContext context)
        {
            Parent = context.GetParent();
            context.Ancestors.Push(this);
            ReadNoHeader(context);
            context.Ancestors.Pop();
            PostReadNoHeader(context);
        }

        public OfficeArtRecord Parent { get; private set; }

        protected virtual void PostReadNoHeader(OfficeArtReadContext context)
        {
        }

        private static OfficeArtRecord CreateRecord(RecType recType)
        {
            Contract.Ensures(Contract.Result<OfficeArtRecord>() != null);
            Contract.Ensures(Contract.Result<OfficeArtRecord>().RecordType == recType ||
                             recType == RecType.OfficeArtBlipJPEG2 &&
                             Contract.Result<OfficeArtRecord>().RecordType == RecType.OfficeArtBlipJPEG);
            switch (recType)
            {
                case 0:
                    throw new NotSupportedException();
                case RecType.OfficeArtFOPT:
                    return new OfficeArtFOPT();
                case RecType.OfficeArtBStoreContainer:
                    return new OfficeArtBStoreContainer();
                case RecType.OfficeArtDggContainer:
                    return new OfficeArtDggContainer();
                case RecType.OfficeArtFBSE:
                    return new OfficeArtFBSE();
                case RecType.OfficeArtFDGGBlock:
                    return new OfficeArtFDGGBlock();
                case RecType.OfficeArtSpContainer:
                    return new OfficeArtSpContainer();
                case RecType.OfficeArtSpgrContainer:
                    return new OfficeArtSpgrContainer();
                case RecType.OfficeArtChildAnchor:
                    return new OfficeArtChildAnchor();
                case RecType.OfficeArtColorMRUContainer:
                    return new OfficeArtColorMRUContainer();
                case RecType.OfficeArtDgContainer:
                    return new OfficeArtDgContainer();
                case RecType.OfficeArtFDG:
                    return new OfficeArtFDG();
                case RecType.OfficeArtFPSPL:
                    return new OfficeArtFPSPL();
                case RecType.OfficeArtFRITContainer:
                    return new OfficeArtFRITContainer();
                case RecType.OfficeArtSecondaryFOPT:
                    return new OfficeArtSecondaryFOPT();
                case RecType.OfficeArtSplitMenuColorContainer:
                    return new OfficeArtSplitMenuColorContainer();
                case RecType.OfficeArtTertiaryFOPT:
                    return new OfficeArtTertiaryFOPT();
                case RecType.OfficeArtBlipDIB:
                    return new OfficeArtBlipDIB();
                case RecType.OfficeArtBlipJPEG:
                    return new OfficeArtBlipJPEG();
                case RecType.OfficeArtBlipJPEG2:
                    return new OfficeArtBlipJPEG();
                case RecType.OfficeArtBlipPICT:
                    return new OfficeArtBlipPICT();
                case RecType.OfficeArtBlipPNG:
                    return new OfficeArtBlipPNG();
                case RecType.OfficeArtBlipTIFF:
                    return new OfficeArtBlipTIFF();
                case RecType.OfficeArtBlipWMF:
                    return new OfficeArtBlipWMF();
                case RecType.OfficeArtBlipEMF:
                    return new OfficeArtBlipEMF();
                case RecType.OfficeArtFSPGR:
                    return new OfficeArtFSPGR();

                case RecType.OfficeArtFSP:
                    return new OfficeArtFSP();
                case RecType.OfficeArtClientAnchorSheet:
                    return new OfficeArtClientAnchorSheet();
                case RecType.OfficeArtClientData:
                    return new OfficeArtClientData();

                case RecType.OfficeArtClientTextbox:
                    return new OfficeArtClientTextbox();
                case RecType.OfficeArtSolverContainer:
                    return new OfficeArtSolverContainer();

                default:
                    throw new NotSupportedException();
            }
        }

#if DEBUG
        public static void TestObsolete()
        {

            foreach (RecType type in Enum.GetValues(typeof (RecType)))
            {
                if ((int) type != 0)
                {
                    OfficeArtRecord o = CreateRecord(type);
                    byte test = o.Version;
                }
            }
        }
#endif

        public static OfficeArtRecord ReadRecord(OfficeArtReadContext context)
        {
            var header = new OfficeArtRecordHeader();
            header.Read(context.Reader);
#if DEBUG
            long position = context.BaseStream.Position;
#endif
            OfficeArtRecord result = CreateRecord(header.recType);
            result.rh = header;
            Contract.Assert(result.rh.recType == result.RecordType);
            result.ReadNoHeaderW(context);
            return result;
        }

        protected abstract void ReadNoHeader(OfficeArtReadContext context);

        public void Write(OfficeArtWriteContext context)
        {
#if DEBUG
            long position = context.BaseStream.Position;
#endif

            rh.recType = RecordType;
            rh.recLen = (uint) ByteLength;
            rh.recVer = Version;
            PrepareHeader(ref rh);
            Contract.Assert(rh.recType == RecordType);
            rh.Write(context.Writer);
            WriteNoHeader(context);
#if DEBUG
            long endPosition = context.BaseStream.Position;
            Contract.Assert((endPosition - position) == FullByteLength);
#endif
        }

        protected abstract byte Version { get; }

        protected abstract int ByteLength { get; }

        internal int FullByteLength
        {
            get { return OfficeArtRecordHeader.ByteLength + ByteLength; }
        }

#if DEBUG
        private bool inCheck;
#endif

        protected abstract void WriteNoHeader(OfficeArtWriteContext context);

        protected virtual void PrepareHeader(ref OfficeArtRecordHeader rh)
        {
        }

    }
}